#======================================================================
# MultiEntityChooser.py
# KnowledgeEngine
#
# Created by John Meredith on 2006-10-18.
# Copyright (c) 2006 Faulkner Technologies. All rights reserved.
#======================================================================

#======================================================================
# KnowledgeEngine imports
#======================================================================
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Assessible import Assessible
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.Components.EntityChooser.EntityChooser import EntityChooserInfo, EntityChooser
from Products.KnowledgeEngine.KERLProcessor import *
from Products.KnowledgeEngine.KERLExtensions import KERLExtensions
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.Graphing.Graphable import *
from Products.KnowledgeEngine.Graphing.Datum import *
from Products.KnowledgeEngine.User import User 
from Products.KnowledgeEngine.Caching import RequestCache 
from Products.KnowledgeEngine.SimpleWizard import *
from Products.KnowledgeEngine.WizardStepDefinition import *
from Products.KnowledgeEngine.WizardState import *

#======================================================================
# Zope CMF Library Imports
#======================================================================
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore import permissions
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.utils import getToolByName

# ======================================================================
# Zope Library Imports
# ======================================================================
from Globals import DTMLFile
from App.ImageFile import ImageFile



# ======================================================================
# MultiEntityChooserInfo Class Definition
# ======================================================================
class MultiEntityChooserInfo(EntityChooserInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """


    #---------------------------------------------------------------#
    def getClassName(self):
        """
            Return the class name
        """
        return 'MultiEntityChooser'


    #---------------------------------------------------------------#
    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Multi Entity Chooser"


    #---------------------------------------------------------------#
    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "mec"


    #---------------------------------------------------------------#
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/MultiEntityChooser_icon.gif', globals())


    #---------------------------------------------------------------#
    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        EntityChooserInfo.onConfigureProperties(self)
        
        # Add simple filtering mechanism
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="filterComponentIdentity",
                displayName="Filter Component Identity (ALPHA)",
                description="The identity of the component whose value will be used to filter the entity source results",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue="",
            )
        )

        self.addPropertyDefinition(
            PropertyDefinition(
                identity="filterPropertyName",
                displayName="Filter Property name (ALPHA)",
                description="The name of the property on an entity which will be filtered against. Note: Case sensitive!",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                defaultValue="",
            )
        )


# ======================================================================
# MultiEntityChooser Class Definition
# ======================================================================
class MultiEntityChooser(EntityChooser):
    """
        The Multiple Entity Chooser Component
    """


    #---------------------------------------------------------------#
    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = MultiEntityChooserInfo()


    #---------------------------------------------------------------#
    def __init__(self, id, identity):
        "Constructor for the MultiEntityChooser"        

        EntityChooser.__init__(self, id, identity)


    #---------------------------------------------------------------#
    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "value":
            return self.getEntitySource().getEntity( assessment.getComponentState(identity).getValue() ).getTitle()
        else:
            return Component.getComponentProperty(self, identity, propertyName, assessment)


    #---------------------------------------------------------------#
    def getFilterComponentValue(self, assessment):
        """ Return the value of the component which will be used to filter """

        identity = self.getFilterComponentIdentity()
        if identity:
            return assessment.getComponentState(identity).getValue()
            
        return None


    #---------------------------------------------------------------#
    def getFilter(self, assessment):
        """
            Construct the filter to be used against the entity source
        """

        if self.getFilterComponentIdentity() and self.getFilterPropertyName():
            return { self.getFilterPropertyName() : self.getFilterComponentValue(assessment) }
        
        return {}


    #---------------------------------------------------------------#
    def getFilterComponentIdentity(self):
        """ Returns the identity of the component who's value will be used to filter """
        
        return self.filterComponentIdentity or None


    #---------------------------------------------------------------#
    def getFilterPropertyName(self):
        """ Returns the name of the entity property to filter against """
        
        return self.filterPropertyName or None


    #---------------------------------------------------------------#
    def getAssessmentValueType(self):
        """
            ABSTRACT: Returns the type of the value that is to be stored
            for this component when it's assessment state is persisted.

            NOTE 1: See AssessmentComponentValueTypes for a list of defined contants
            NOTE 2: This method MUST be overriden in the implementing
            class to specify the correct type for value persistence

            RETURN TYPE: String
        """

        #entity chooser assessment values are a string of entity identies chosen
        return st_STRING


    #---------------------------------------------------------------#
    def getDefaultAssessmentState(self, assessment):
        """
            ABSTRACT: Constructs and returns a suitable 'default'
            AssessmentComponentState for the provided Assessment
            instance.

            NOTE: This method MUST be overriden in the implementing
            class to provide a suitable default value for the component

            RETURN TYPE: AssessmentComponentState
        """

        return AssessmentComponentState(
            assessment.getIdentity(),
            assessment.getModelIdentity(),
            self.getIdentity(),
            self.getAssessmentValueType(),
            "",
            revision=assessment.getRevision()
        )


    #---------------------------------------------------------------#
    def updateAssessmentState(self, assessment, formValue):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.

            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
        
        Component.updateAssessmentState(self, assessment, formValue)
        value = formValue
        
        #get the current assessment state for the component from the assessment
        state = assessment.getComponentState(self.getIdentity())

        state.setValue(value)

        #update the assessment
        assessment.setIsModified(true)
        
    
    #---------------------------------------------------------------#
    def isMultiValued(self):
        """
            Returns if the component is expecting a list ie. can store
            multiple values
        """
        
        return true


    #---------------------------------------------------------------#
    def getAssessmentWizardAndWizardState(self, assessment, wizardName):
        """
            ABSTRACT: Returns a tuple containing a Wizard instance and
            an associated WizardState instance (Wizard, WizardState)
            for the specified assessment and wizardName.
            
            This method is used to initialise a named assessment wizard
            that will be used during the assessment process on behalf
            of this component.
            
            A value of (None, None) is returned if there is no wizard
            available for the specified wizardName. (or assessment 
            wizards are not supported for the component)
            
            RETURN TYPE: (Wizard, WizardState)
        """ 

        #construct an 'EntityFinder' wizard?
        if wizardName == 'EntityFinder':
            #define the steps
            step1 = WizardStepDefinition("%s Finder" % self.getEntitySource().getEntityNameSingular(), 
                                         "Choose one or more %s from the list of %s below" % (self.getEntitySource().getEntityNamePlural(), self.getEntitySource().getEntityNamePlural()))
            
            #a list of entities to choose from
            entityMappingList = [(self.renderEntity(None, entity=entity), entity.getIdentity()) for entity in self.getEntitySource().getEntities(filter=self.getFilter(assessment))]
            
            step1.addPropertyDefinition(
                PropertyDefinition(
                    identity="entityIdentity",
                    displayName="Available %s:" % self.getEntitySource().getEntityNamePlural(),
                    defaultValue=[],
                    description="",
                    propertyType=pt_INTEGER,
                    propertyStructure=ps_LIST,
                    propertyFormat=pf_NATIVE,
                    hasEnumeratedValues=true,
                    enumeratedValuesMapping=entityMappingList,
                )
            )
            
            #define the wizard
            wizard = SimpleWizard([step1])
            
            #initialise the wizard state (with initial selections)
            wizardState = WizardState()
            wizardState.setPropertyValue("entityIdentity", assessment.getComponentState(self.getIdentity()).getValue())
            
            #return what we've created so that we can create a wizard with it
            return (wizard, wizardState)
        
        else:
            #unknown wizard requested
            return (None,None)


    #---------------------------------------------------------------#
    def executeAssessmentWizard(self, assessment, wizardName, wizard, wizardState):
        """
            ABSTRACT: Executes the specified assessment wizard with
            the provided wizard state.
            
            Override this method to implement assessment wizard processing
            
            RETURN TYPE: None
        """

        #handle the EnityFinder
        if wizardName == 'EntityFinder':

            #get the existing component state
            state = assessment.getComponentState(self.getIdentity())

            #set the component state to the select entity identity
            state.setValue(wizardState.getPropertyValue('entityIdentity'))


    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#
    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        
        #example: cond="['ec','selected', 'id1']" 
        #returns 'document.getElementById("ec").value=='id1'

        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #determine the operator we are going to process
        opr = expression[1]


        result = "true"

        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":
            result = "document.getElementById(\"%s\").value == \"%s\"" % (identity, asJavaScript(expression[2]))
            
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":
            result = "document.getElementById(\"%s\").value != \"%s\"" % (identity, asJavaScript(expression[2]))
        
        # An answer is empty if nothing is selected. We just check if there is
        # an element given by the id
        elif opr == "empty":
            result = "document.getElementById(\"%s\") === null" % identity

        # An answer is selected if something is selected
        elif opr == "notempty":
            result = "document.getElementById(\"%s\").value != \"\"" % identity

        return result


    #---------------------------------------------------------------#
    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        #determine the operator we are going to process
        operator = expression[1]

        result = ""
        
        #the entity is 'selected' 
        if operator == "selected":
            result = "%s '%s' %s is selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())
            
        #the entity is 'unselected' 
        elif operator == "unselected":
            result = "%s '%s' %s is not selected" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())
            
        #the entity is 'empty' 
        elif operator == "empty":
            result = "%s '%s' %s is empty" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())
            
        #the entity is 'notempty' 
        elif operator == "notempty":
            result = "%s '%s' %s is not empty" % (self.getClassInfo().getDisplayName(), self.getTitleOrId(), self.getEntitySource().getEntity(expression[2]).getTitle())

        return result


    #---------------------------------------------------------------#
    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #get the stored state
        state = assessment.getComponentState(identity)

        #determine the operator we are going to process
        opr = expression[1]

        result = true

        #an answer is 'selected' if the actual selected answer is the specified answer
        if opr == "selected":
            result = state.getValue() == expression[2]
            
        #an answer is 'unselected' if the actual selected answer isn't the specified answer
        elif opr == "unselected":
            result = state.getValue() != expression[2]
        
        # An answer is empty if nothing is selected
        elif opr == "empty":
            result = not not state.getValue()
        
        # An answer is selected if something is selected
        elif opr == "notempty":
            result = not state.getValue()
        
        return result
    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#



# ======================================================================
# Register Component Information with the Knowledge Engine
# ======================================================================
registerGenericObjectClass(MultiEntityChooser)
